#include "objmodelclass.h"
ObjModelClass::ObjModelClass()
{
	m_Texture = 0;
}


ObjModelClass::ObjModelClass(const ObjModelClass& other)
{
}


ObjModelClass::~ObjModelClass()
{
}
bool ObjModelClass::Initialize(OpenGLClass* OpenGL, char* textureFilename, unsigned int textureUnit, bool wrap)
{
	bool result;


	// Initialize the vertex and index buffer that hold the geometry for the triangle.
	result = InitializeBuffers(OpenGL);
	if (!result)
	{
		return false;
	}

	result = LoadTexture(OpenGL, textureFilename, textureUnit, wrap);
	if (!result)
	{
		return false;
	}

	return true;
}
void ObjModelClass::Shutdown(OpenGLClass* OpenGL)
{
	// Release the texture used for this model.
	ReleaseTexture();

	// Release the vertex and index buffers.
	ShutdownBuffers(OpenGL);

	return;
}
void ObjModelClass::Render(OpenGLClass* OpenGL)
{
	// Put the vertex and index buffers on the graphics pipeline to prepare them for drawing.
	RenderBuffers(OpenGL);

	return;
}

bool ObjModelClass::load_obj_file(const char* file_name,float*& points,float*& tex_coords,float*& normals,int& point_count)
{

	float* unsorted_vp_array = NULL;
	float* unsorted_vt_array = NULL;
	float* unsorted_vn_array = NULL;
	int current_unsorted_vp = 0;
	int current_unsorted_vt = 0;
	int current_unsorted_vn = 0;

	FILE* fp = fopen(file_name, "r");
	if (!fp) {
		fprintf(stderr, "ERROR: could not find file %s\n", file_name);
		return false;
	}

	// first count points in file so we know how much mem to allocate
	point_count = 0;
	int unsorted_vp_count = 0;
	int unsorted_vt_count = 0;
	int unsorted_vn_count = 0;
	int face_count = 0;
	char line[1024];
	while (fgets(line, 1024, fp))  {
		if (line[0] == 'v') {
			if (line[1] == ' ') {
				unsorted_vp_count++;
			}
			else if (line[1] == 't') {
				unsorted_vt_count++;
			}
			else if (line[1] == 'n') {
				unsorted_vn_count++;
			}
		}
		else if (line[0] == 'f') {
			face_count++;
		}
	}

	unsorted_vp_array = (float*)malloc(unsorted_vp_count * 3 * sizeof(float));
	unsorted_vt_array = (float*)malloc(unsorted_vt_count * 2 * sizeof(float));
	unsorted_vn_array = (float*)malloc(unsorted_vn_count * 3 * sizeof(float));
	points = (float*)malloc(3 * face_count * 3 * sizeof(float));
	tex_coords = (float*)malloc(3 * face_count * 2 * sizeof(float));
	normals = (float*)malloc(3 * face_count * 3 * sizeof(float));
	rewind(fp);
	while (fgets(line, 1024, fp)) {
		// vertex
		if (line[0] == 'v') {

			// vertex point
			if (line[1] == ' ') {
				float x, y, z;
				x = y = z = 0.0f;
				sscanf(line, "v %f %f %f", &x, &y, &z);
				unsorted_vp_array[current_unsorted_vp * 3] = x;
				unsorted_vp_array[current_unsorted_vp * 3 + 1] = y;
				unsorted_vp_array[current_unsorted_vp * 3 + 2] = z;
				current_unsorted_vp++;

				// vertex texture coordinate
			}
			else if (line[1] == 't') {
				float s, t;
				s = t = 0.0f;
				sscanf(line, "vt %f %f", &s, &t);
				unsorted_vt_array[current_unsorted_vt * 2] = s;
				unsorted_vt_array[current_unsorted_vt * 2 + 1] = t;
				current_unsorted_vt++;

				// vertex normal
			}
			else if (line[1] == 'n') {
				float x, y, z;
				x = y = z = 0.0f;
				sscanf(line, "vn %f %f %f", &x, &y, &z);
				unsorted_vn_array[current_unsorted_vn * 3] = x;
				unsorted_vn_array[current_unsorted_vn * 3 + 1] = y;
				unsorted_vn_array[current_unsorted_vn * 3 + 2] = z;
				current_unsorted_vn++;
			}

			// faces
		}
		else if (line[0] == 'f') {
			// work out if using quads instead of triangles and print a warning
			int slashCount = 0;
			int len = strlen(line);
			for (int i = 0; i < len; i++) {
				if (line[i] == '/') {
					slashCount++;
				}
			}
			if (slashCount != 6) {
				fprintf(
					stderr,
					"ERROR: file contains quads or does not match v vp/vt/vn layout - \
					make sure exported mesh is triangulated and contains vertex points, \
					texture coordinates, and normals\n"
				);
				return false;
			}

			int vp[3], vt[3], vn[3];
			sscanf(
				line,
				"f %i/%i/%i %i/%i/%i %i/%i/%i",
				&vp[0], &vt[0], &vn[0], &vp[1], &vt[1], &vn[1], &vp[2], &vt[2], &vn[2]
			);

			/* start reading points into a buffer. order is -1 because obj starts from
			1, not 0 */
			// NB: assuming all indices are valid
			for (int i = 0; i < 3; i++) {
				if ((vp[i] - 1 < 0) || (vp[i] - 1 >= unsorted_vp_count)) {
					fprintf(stderr, "ERROR: invalid vertex position index in face\n");
					return false;
				}
				if ((vt[i] - 1 < 0) || (vt[i] - 1 >= unsorted_vt_count)) {
					fprintf(stderr, "ERROR: invalid texture coord index %i in face.\n", vt[i]);
					return false;
				}
				if ((vn[i] - 1 < 0) || (vn[i] - 1 >= unsorted_vn_count)) {
					printf("ERROR: invalid vertex normal index in face\n");
					return false;
				}
				points[point_count * 3] = unsorted_vp_array[(vp[i] - 1) * 3];
				points[point_count * 3 + 1] = unsorted_vp_array[(vp[i] - 1) * 3 + 1];
				points[point_count * 3 + 2] = unsorted_vp_array[(vp[i] - 1) * 3 + 2];
				tex_coords[point_count * 2] = unsorted_vt_array[(vt[i] - 1) * 2];
				tex_coords[point_count * 2 + 1] = unsorted_vt_array[(vt[i] - 1) * 2 + 1];
				normals[point_count * 3] = unsorted_vn_array[(vn[i] - 1) * 3];
				normals[point_count * 3 + 1] = unsorted_vn_array[(vn[i] - 1) * 3 + 1];
				normals[point_count * 3 + 2] = unsorted_vn_array[(vn[i] - 1) * 3 + 2];
				point_count++;
			}
		}
	}
	fclose(fp);
	free(unsorted_vp_array);
	free(unsorted_vn_array);
	free(unsorted_vt_array);
	printf(
		"allocated %i points\n",
		point_count
	);
	return true;
}
bool ObjModelClass::InitializeBuffers(OpenGLClass* OpenGL)
{
	GLfloat* vp = NULL; // array of vertex points
	GLfloat* vn = NULL; // array of vertex normals
	GLfloat* vt = NULL; // array of texture coordinates
	int g_point_count = 0;
	if (!load_obj_file("../shader/data/sphere.obj", vp, vt, vn, g_point_count)) {
		return false;
	}

	GLuint vao;
	OpenGL->glGenVertexArrays(1, &vao);
	OpenGL->glBindVertexArray(vao);
	GLuint points_vbo;
	OpenGL->glGenBuffers(1, &points_vbo);
	if (NULL != vp) {

		OpenGL->glBindBuffer(GL_ARRAY_BUFFER, points_vbo);
		OpenGL->glBufferData(GL_ARRAY_BUFFER, 3 * g_point_count * sizeof(GLfloat), vp, GL_STATIC_DRAW);
		OpenGL->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
		OpenGL->glEnableVertexAttribArray(0);
	}
	if (NULL != vt)
	{

		OpenGL->glBindBuffer(GL_ARRAY_BUFFER, points_vbo);
		OpenGL->glBufferData(GL_ARRAY_BUFFER, 2 * g_point_count * sizeof(GLfloat), vt, GL_STATIC_DRAW);
		OpenGL->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
		OpenGL->glEnableVertexAttribArray(1);
	}
	if (NULL != vn)
	{
		OpenGL->glBindBuffer(GL_ARRAY_BUFFER, points_vbo);
		OpenGL->glBufferData(GL_ARRAY_BUFFER, 3 * g_point_count * sizeof(GLfloat), vn, GL_STATIC_DRAW);
		OpenGL->glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, NULL);
		OpenGL->glEnableVertexAttribArray(2);
	}
	delete[] vt;
	delete[] vn;
	delete[] vp;
	return true;
}
void ObjModelClass::ShutdownBuffers(OpenGLClass* OpenGL)
{
	// Disable the two vertex array attributes.
	OpenGL->glDisableVertexAttribArray(0);
	OpenGL->glDisableVertexAttribArray(1);

	// Release the vertex buffer.
	OpenGL->glBindBuffer(GL_ARRAY_BUFFER, 0);
	OpenGL->glDeleteBuffers(1, &m_vertexBufferId);

	// Release the index buffer.
	OpenGL->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	OpenGL->glDeleteBuffers(1, &m_indexBufferId);

	// Release the vertex array object.
	OpenGL->glBindVertexArray(0);
	OpenGL->glDeleteVertexArrays(1, &m_vertexArrayId);

	return;
}


void ObjModelClass::RenderBuffers(OpenGLClass* OpenGL)
{
	// Bind the vertex array object that stored all the information about the vertex and index buffers.
	OpenGL->glBindVertexArray(m_vertexArrayId);

	// Render the vertex buffer using the index buffer.
	glDrawElements(GL_TRIANGLES, m_indexCount, GL_UNSIGNED_INT, 0);

	return;
}

//LoadTexture is a new private function that will create the texture object and then initialize it with the input file name provided.This function is called during initialization.

bool ObjModelClass::LoadTexture(OpenGLClass* OpenGL, char* textureFilename, unsigned int textureUnit, bool wrap)
{
	bool result;


	// Create the texture object.
	m_Texture = new TextureClass;
	if (!m_Texture)
	{
		return false;
	}

	// Initialize the texture object.
	result = m_Texture->Initialize(OpenGL, textureFilename, textureUnit, wrap);
	if (!result)
	{
		return false;
	}

	return true;
}

//The ReleaseTexture function will release the texture object that was created and loaded during the LoadTexture function.

void ObjModelClass::ReleaseTexture()
{
	// Release the texture object.
	if (m_Texture)
	{
		m_Texture->Shutdown();
		delete m_Texture;
		m_Texture = 0;
	}

	return;
}

